#include <cstdlib>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>

#include "../../../../SDK/include/tinyxml/tinyxml.h"

#include "../../projectevent.h"
#include "../../projecteventbus.h"
#include "../../project_p.h"

#include "cineprojectmanager.h"

INIT_PROJECT_MANAGER_PLUGIN_FACTORY( CineProjectManager )
INIT_PROJECT_MANAGER_PLUGIN_NAME( "Cine Project Manager Plugin\0" )
INIT_PROJECT_MANAGER_PLUGIN_VERSION( -1 )
INIT_PROJECT_MANAGER_PLUGIN_AUTHOR( "The Cine Developer" )

void CineProjectManager::RegisterEvent( ProjectEventBus* eventBus )
{
    /// import/export
    eventBus->AttachListener( Events::ProjectImport, this );
    eventBus->AttachListener( Events::ProjectExport, this );

    /// tmp FIXME: remove it ;)
//     eventBus->AttachListener( EventType_ProjectPrintInfo, this );
}

bool CineProjectManager::HandleEvent( Events::Event* event )
{
    /// NOTE: initialize shared d-pointer here
    switch ( event->GetType() ) {
        /// import/export
        case Events::ProjectImport: {
            d = ((ProjectImportEvent*)(event))->d;
            std::string str = d->project_mainfile;//((ProjectImport*)(event))->data;
            importProjectFiles( str );
            break;
        }
        case Events::ProjectExport: {
            d = ((ProjectExportEvent*)(event))->d;
            std::string str = d->project_mainfile;//((ProjectExport*)(event))->data;
            exportProjectFiles( str );
            break;
        }
//         case Events::ProjectPrintInfo:
//             d = ((ProjectPrintInfo*)(event))->d;
//             printInfo();
//             break;
        /// tmp member
        default:
            std::cerr << "Unexpected Event Here!" << std::endl;
    }
    std::cerr << "catch event from cine project sub plugin" << std::endl;
    return false;
}

void CineProjectManager::importProjectFiles( const std::string& inbasefile )
{
    std::cerr << "import: " << inbasefile << std::endl;
    TiXmlDocument fp( inbasefile.c_str() );
    if ( !fp.LoadFile() )
        return;

    ProjectTarget target;
    ProjectBuildOptionFlags optflags;

/// WARNING: Caution! No do-while marco guard
#define EXTRACTSTRINFO2VECTOR(Node,Key,VECTOR) \
if ( std::string(Node->Value()) == #Key ) { \
    TiXmlElement* __##Key##Node = Node->FirstChildElement(); \
    while ( __##Key##Node ) { \
        VECTOR.push_back( std::string( __##Key##Node->GetText() ) ); \
        __##Key##Node = __##Key##Node->NextSiblingElement(); \
    } \
    Node = Node->NextSiblingElement(); \
    continue; \
}

/// WARNING: Caution! No do-while marco guard
#define EXTRACTSTRINFO2SET(Node,Key,SET) \
if ( std::string(Node->Value()) == #Key ) { \
    TiXmlElement* __##Key##Node = Node->FirstChildElement(); \
    while ( __##Key##Node ) { \
        SET.insert( std::string( __##Key##Node->GetText() ) ); \
        __##Key##Node = __##Key##Node->NextSiblingElement(); \
    } \
    Node = Node->NextSiblingElement(); \
    continue; \
}

/// WARNING: Caution! No do-while marco guard
#define EXTRACTSTRINFO2STR(Node,Key,STR) \
if ( std::string(Node->Value()) == #Key ) { \
    STR = Node->GetText(); \
    Node = Node->NextSiblingElement(); \
    continue; \
}

/// WARNING: Caution! No do-while marco guard
#define EXTRACTSTRINFO2BOOL(Node,Key,BOOL) \
if ( std::string(Node->Value()) == #Key ) { \
    BOOL = (Node->GetText()=="0")?false:true; \
    Node = Node->NextSiblingElement(); \
    continue; \
}

/// WARNING: Caution! No do-while marco guard
#define EXTRACTSTRINFO2INT(Node,Key,INT) \
if ( std::string(Node->Value()) == #Key ) { \
    INT = atoi(Node->GetText()); \
    Node = Node->NextSiblingElement(); \
    continue; \
}

    TiXmlElement* root = fp.RootElement();
    TiXmlElement* node = root->FirstChildElement();
    while ( node ) {
        if ( node->Value() ) {
            /// project name
            EXTRACTSTRINFO2STR( node, name, d->project_name );
            /// include directories
            EXTRACTSTRINFO2VECTOR( node, includes, target.project_includedirs );
            /// sources files
            EXTRACTSTRINFO2SET( node, sources, target.extracted_files );
            /// link libraries
            EXTRACTSTRINFO2VECTOR( node, linking, target.project_linktargets );
            /// preprocessor marcos
            EXTRACTSTRINFO2VECTOR( node, preprocessor, optflags.defined_marcos );
            /// opt flags
            if ( std::string( node->Value() ) == "flags" ) {
                TiXmlElement* flag = node->FirstChildElement();
                while ( flag ) {
                    if ( flag->Value() ) {
                        /// optimize
                        EXTRACTSTRINFO2BOOL( flag, optimize_level, optflags.optimize_level );
                        /// compiler
                        EXTRACTSTRINFO2BOOL( flag, fast_float_point, optflags.fast_float_point );
                        EXTRACTSTRINFO2INT( flag, check_func_heapstack, optflags.check_func_heapstack );
                        /// misc
                        EXTRACTSTRINFO2INT( flag, with_debug_symbol, optflags.with_debug_symbol );
                        EXTRACTSTRINFO2INT( flag, warning_all, optflags.warning_all );
                        EXTRACTSTRINFO2INT( flag, warning_as_error, optflags.warning_as_error )
                        else {
                            std::cerr << "unknown node: " << flag->Value() << std::endl;
                        }
                    }
                    flag = flag->NextSiblingElement();
                }
            }
            else {
                std::cerr << "unknown node: " << node->Value() << std::endl;
            }
        }
        node = node->NextSiblingElement();
    }

    d->project_targets.clear();
    d->project_targets.push_back( target );

    d->is_modified = false;

#undef EXTRACTSTRINFO2INT
#undef EXTRACTSTRINFO2BOOL
#undef EXTRACTSTRINFO2STR
#undef EXTRACTSTRINFO2SET
#undef EXTRACTSTRINFO2VECTOR
}

void CineProjectManager::exportProjectFiles( const std::string& outbasefile )
{
    std::cerr << "export: " << outbasefile << std::endl;
    TiXmlDocument fp;

    TiXmlDeclaration* decl = new TiXmlDeclaration( "1.0", "", "" );
    fp.LinkEndChild( decl );

    TiXmlElement* root = new TiXmlElement( "CineProject" );
    fp.LinkEndChild( root );

    TiXmlElement* elem;
    char buf[10];
    ProjectTarget target = d->project_targets[0];// NOTE: multi targets support?
    ProjectBuildOptionFlags optflags = d->optflags;

#define APPENDSTRINFO(Node,Key,Value) \
do { \
TiXmlElement* __##Key##description = new TiXmlElement( #Key ); \
TiXmlText* __##Key = new TiXmlText( Value ); \
__##Key##description->LinkEndChild( __##Key ); \
Node->LinkEndChild( __##Key##description ); \
} while(0);

#define APPENDBOOLINFO(Node,Key,Value) \
APPENDSTRINFO(Node,Key,((Value)?"1":"0"))

#define APPENDINTINFO(Node,Key,Value) \
do { \
sprintf( buf, "%d", Value ); \
APPENDSTRINFO(Node,Key,buf) \
} while(0);

#define APPENDVECTORINFO(Node,Key,Subkey,VECTOR,TYPE) \
do { \
TiXmlElement* __##Key##description = new TiXmlElement( #Key ); \
Node->LinkEndChild( __##Key##description ); \
size_t size = VECTOR.size(); \
for ( size_t i=0; i<size; ++i ) { \
    APPEND##TYPE##INFO(__##Key##description,Subkey,VECTOR[i].c_str()); \
} \
} while(0);

#define APPENDSETINFO(Node,Key,Subkey,SET,TYPE,stdtype) \
do { \
TiXmlElement* __##Key##description = new TiXmlElement( #Key ); \
Node->LinkEndChild( __##Key##description ); \
std::set<stdtype>::const_iterator first = SET.begin(); \
std::set<stdtype>::const_iterator end = SET.end(); \
while ( first != end ) { \
    APPEND##TYPE##INFO(__##Key##description,SubKey,(*first).c_str() ); \
    ++first; \
} \
} while(0);

    /// project name
    APPENDSTRINFO( root, name, d->project_name.c_str() );
    /// include directories
    APPENDVECTORINFO( root, includes, dir, target.project_includedirs, STR );
    /// sources files
    APPENDSETINFO( root, sources, file, target.extracted_files, STR, std::string );
    /// link libraries
    APPENDVECTORINFO( root, linking, library, target.project_linktargets, STR );
    /// preprocessor marcos
    APPENDVECTORINFO( root, preprocessor, defined_marcos, optflags.defined_marcos, STR );
    /// opt flags
    {
        elem = new TiXmlElement( "flags" );
        root->LinkEndChild( elem );

        /// optimize
        APPENDBOOLINFO( elem, optimize_level, optflags.optimize_level );
        /// compiler
        APPENDBOOLINFO( elem, fast_float_point, optflags.fast_float_point );
        APPENDINTINFO( elem, check_func_heapstack, optflags.check_func_heapstack );
        /// misc
        APPENDINTINFO( elem, with_debug_symbol, optflags.with_debug_symbol );
        APPENDINTINFO( elem, warning_all, optflags.warning_all );
        APPENDINTINFO( elem, warning_as_error, optflags.warning_as_error );
    }

    fp.SaveFile( outbasefile.c_str() );

    d->is_modified = false;

#undef APPENDSETINFO
#undef APPENDVECTORINFO
#undef APPENDINTINFO
#undef APPENDBOOLINFO
#undef APPENDSTRINFO
}

void CineProjectManager::printInfo()
{
    std::cerr << "=================================" << std::endl;
    std::cerr << "project:\t" << d->project_name << std::endl << std::endl;
    for ( int q=0; q<d->project_targets.size(); q++ ) {
        ProjectTarget target = d->project_targets[q];
        std::cerr << "target:\t" << target.target_bin << std::endl;
        std::cerr << "files:\t";
        for ( int i=0; i<target.target_files.size(); i++ )
            std::cerr << target.target_files[i] << ' ';
        std::cerr << std::endl;
        std::cerr << "Ex:\t";
        std::set<std::string>::const_iterator first = target.extracted_files.begin();
        std::set<std::string>::const_iterator end = target.extracted_files.end();
        while ( first != end ) {
            std::cerr << *first << ' ';
            ++first;
        }
        std::cerr << std::endl;
        std::cerr << "inclu:\t";
        for ( int i=0; i<target.project_includedirs.size(); i++ )
            std::cerr << target.project_includedirs[i] << ' ';
        std::cerr << std::endl;
        std::cerr << "link:\t";
        for ( int i=0; i<target.project_linktargets.size(); i++ )
            std::cerr << target.project_linktargets[i] << ' ';
        std::cerr << std::endl;
        std::cerr << "sets:\t";
        std::map< std::string, std::vector<std::string> >::const_iterator it = target.target_filesets.begin();
        for ( ; it!=target.target_filesets.end(); it++ ) {
            std::cerr << (*it).first << " --- ";
            for ( int i=0; i<(*it).second.size(); i++ )
                std::cerr << (*it).second[i] << ' ';
            std::cerr << std::endl;
        }
        std::cerr << std::endl;
    }
}
